<!DOCTYPE html>
<html lang="zh">
    <head>
  <!-- 元数据 -->
  <meta charset="utf-8">
  
  
  <title>Unity网络游戏开发(2) | 舛羽的笔记小站</title>
  <meta name="author" content="cy" />
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="robots" content="index,follow" />
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <meta name="format-detection" content="telphone=no, email=no" />
  
    <meta name="keywords" content="unity, 网络游戏" />
  
  <meta name="description" content="Unity网络游戏开发第二部分">
<meta property="og:type" content="article">
<meta property="og:title" content="Unity网络游戏开发(2)">
<meta property="og:url" content="http://example.com/2022/11/16/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91-2/index.html">
<meta property="og:site_name" content="舛羽的笔记小站">
<meta property="og:description" content="Unity网络游戏开发第二部分">
<meta property="og:locale">
<meta property="og:image" content="https://s2.loli.net/2022/10/18/dtRNLmw3BSsu8QZ.png">
<meta property="og:image" content="https://s2.loli.net/2022/10/18/luA6Kokne487wPt.png">
<meta property="og:image" content="https://s2.loli.net/2022/10/18/H4ZrXBbIhmWwexl.png">
<meta property="og:image" content="https://s2.loli.net/2022/10/19/9Fu3RJ4XCWhrHAU.png">
<meta property="article:published_time" content="2022-11-16T07:23:53.000Z">
<meta property="article:modified_time" content="2022-11-16T07:48:51.343Z">
<meta property="article:author" content="cy">
<meta property="article:tag" content="网络游戏">
<meta property="article:tag" content="unity">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://s2.loli.net/2022/10/18/dtRNLmw3BSsu8QZ.png">
  
  <!-- 站点验证相关 -->
  
    
    
    
  
  <!-- 样式表文件 -->
  <link rel="stylesheet" id="kratos-css" href="/css/kratosr.min.css" media="all"></script>
  
    <link rel="stylesheet" id="darkmode-css" href="/css/kr-color-dark.min.css" media="(prefers-color-scheme: dark)"></script>
    <script src="/js/kr-dark.min.js"></script>
  
  
    <link rel="stylesheet" id="highlight-css" href="/css/highlight/night-eighties.min.css" media="all"></script>
  
  
  <link rel="stylesheet" id="fontawe-css" href="/vendors/font-awesome@4.7.0/css/font-awesome.min.css" media="all"></script>
  <link rel="stylesheet" id="nprogress-css" href="/vendors/nprogress@0.2.0/nprogress.css" media="all"></script>
  
  
  
    <link rel="stylesheet" href="/vendors/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css"></script>
  
  <!-- 不得不预先加载的一些JS文件 -->
  <script src="/vendors/jquery@3.6.0/dist/jquery.min.js"></script>
  
    <script src="/vendors/qrcode_js@1.0.0/qrcode.min.js"></script>
  
  
  <style>
    
      .kratos-cover.kratos-cover-2 {
        background-image: url('/images/banner.webp');
      }
    
    
      @media(min-width:768px) {
        body.custom-background {
          background-image: url('/images/bg.webp');
        }
      }
    
  </style>
  
<meta name="generator" content="Hexo 7.3.0"></head>


    <body class="custom-background">
        <div id="kratos-wrapper">
    <div id="kratos-page">
        <div id="kratos-header">
            <header id="kratos-desktop-topnav" class="kratos-topnav">
                <div class="container">
                    <div class="nav-header">
                        <nav id="kratos-menu-wrap">
                            <ul id="kratos-primary-menu" class="sf-menu">
                                
                                    
                                    
                                
                            </ul>
                        </nav>
                    </div>
                </div>
            </header>
            <header id="kratos-mobile-topnav" class="kratos-topnav">
                <div class="container">
                    <div class="color-logo"><a href="/">舛羽的笔记小站</a></div>
                    <div class="nav-toggle">
                        <a class="kratos-nav-toggle js-kratos-nav-toggle">
                            <i></i>
                        </a>
                    </div>
                </div>
            </header>
        </div>
        <div class="kratos-start kratos-hero-2">
            <!-- <div class="kratos-overlay"></div> -->
            <div class="kratos-cover kratos-cover-2 text-center">
                <div class="desc desc2 animate-box">
                    <a href="/">
                        <h2>舛羽的笔记小站</h2> <br />
                        <span></span>
                    </a>
                </div>
            </div>
        </div>

        <div id="kratos-blog-post">
            <div class="container">
                <div id="main" class="row">
                    

        

            <section class="col-md-8">

        

            <article itemscope itemtype="https://schema.org/Article">
    
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/11/16/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91-2/">
    <div class="kratos-hentry kratos-post-inner clearfix">
        <header class="kratos-entry-header">
            
                <h1 class="kratos-entry-title text-center" itemprop="name headline">Unity网络游戏开发(2)</h1>
            
            
            <ul class="kratos-post-meta text-center">
                <li><time datetime="2022-11-16T07:23:53.000Z" itemprop="datePublished"><i class="fa fa-calendar"></i> 2022-11-16</time></li>
                <li itemprop="author" itemscope itemtype="https://schema.org/Person">
                    <i class="fa fa-user"></i> 作者 <span itemprop="name">cy</span>
                </li>
                <li>
                    <i class="fa fa-edit"></i> 
                    
                    
                        ~12.93K
                    
                    字
                </li>
                
            </ul>
        </header>
        <div class="kratos-post-content">
            
            <div id="expire-alert" class="alert alert-warning hidden" role="alert">
                <div class="icon"><i class="fa fa-warning"></i></div>
                <div class="text"><p>本文最后编辑于 <time datetime="1668584931343"></time> 前，其中的内容可能需要更新。</p></div>
            </div>
            
            
            
            <hr />
            <div itemprop="articleBody"><p>Unity网络游戏开发第二部分</p>
<span id="more"></span>
<h2 id="第四章-正确收发数据流"><a href="#第四章-正确收发数据流" class="headerlink" title="第四章 正确收发数据流"></a>第四章 正确收发数据流</h2><p>第四章和第五章会涉及到一些TCP的一些机制</p>
<h3 id="4-1-TCP数据流"><a href="#4-1-TCP数据流" class="headerlink" title="4.1 TCP数据流"></a>4.1 TCP数据流</h3><p>我们都听说过一句话TCP是一种流模式协议，UDP是一种数据报模式的协议。</p>
<p>TCP的流模式指的就是发送的数据如同水流一样发送方将数据发送出去时操作系统会将数据放在发送缓存中，然后接收方在接收前也会先将数据放入接收缓存中，而读取的速度取决于设备和操作系统，这也就意味着发送的次数与接收的次数并不一定相同。你发送的数据量不能大于对方的接收缓存（流量控制），如果你硬是要发送过量数据，则对方的缓存满了就会把多出的数据丢弃。</p>
<p>UDP和TCP不同，发送端调用了几次write，接收端必须用相同次数的read读完。UDP是基于报文的，在接收的时候，每次最多只能读取一个报文，报文和报文是不会合并的，如果缓冲区小于报文长度，则多出的部分会被丢弃。</p>
<h3 id="4-2-粘包与半包"><a href="#4-2-粘包与半包" class="headerlink" title="4.2 粘包与半包"></a>4.2 粘包与半包</h3><p>基于TCP的流模式就会导致一个比较关键的问题，如果客户端在极短的时间内发送多个数据，就会导致接收的时候将两段代码合并在一起导致解析错误之类的问题。</p>
<p>我们看一下大乱斗游戏的一段代码。</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//处理刚进入游戏时需要同步的数据</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//发送同步的数据</span></span><br><span class="line">    NetManager.Send(sendStr);</span><br><span class="line">    StartCoroutine(onewait()); </span><br><span class="line">&#125;</span><br><span class="line"><span class="function">IEnumerator <span class="title">onewait</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">yield</span> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title">WaitForSeconds</span>(<span class="params"><span class="number">0.1f</span></span>)</span>;</span><br><span class="line">    <span class="comment">//请求服务器上所有客户端的数据</span></span><br><span class="line">    NetManager.Send(<span class="string">&quot;List|&quot;</span>);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里我用了一个协程让程序等0.1秒之后再请求服务器上其他玩家的数据，为什么呢？我们把他改成连续发送会怎样呢？</p>
<p>我们先看看协程方式下服务端收到的消息</p>
<p><img src="https://s2.loli.net/2022/10/18/dtRNLmw3BSsu8QZ.png" alt=""></p>
<p>再将程序改成连续发送</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//处理刚进入游戏时需要同步的数据</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//发送同步的数据</span></span><br><span class="line">    NetManager.Send(sendStr);</span><br><span class="line">    <span class="comment">//StartCoroutine(onewait()); </span></span><br><span class="line">    NetManager.Send(<span class="string">&quot;List|&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><img src="https://s2.loli.net/2022/10/18/luA6Kokne487wPt.png" alt=""></p>
<p>可以看到服务端将连续两次发的消息合成了一句消息，导致了服务端程序无法解析，导致程序报错并退出，如果正在运营的网游出现这种情况导致服务器停机，这样会造成很大的损失。</p>
<p>这个现象被称为粘包现象，那么半包现象也好理解了，运气较差的话操作系统将一半的指令读取了，这样也会给服务端造成比较那一处理的麻烦。</p>
<p>一般有三种方法可以解决粘包与半包问题：</p>
<h4 id="4-2-1-长度信息法"><a href="#4-2-1-长度信息法" class="headerlink" title="4.2.1 长度信息法"></a>4.2.1 长度信息法</h4><p>这个方法是在每个数据包前加上长度信息，比如传输hello就变成5hello，一个字节最多表示255个字节，稍微有点少，所以一般会选择Int16或者Int32来表示数据长度，这样再服务端处理的时候就可以先放进一个缓冲区，通过程序保证每一次都能拿到对应的发送信息。</p>
<h4 id="4-2-2-固定长度法"><a href="#4-2-2-固定长度法" class="headerlink" title="4.2.2 固定长度法"></a>4.2.2 固定长度法</h4><p>规定每条信息的长度，如果一条信息短于这个规定长度则用规定的字符将信息填充到这个固定长度，比如规定长度为10，我要发送hello，规定用.来补全，就变成了hello…..了。</p>
<h4 id="4-2-3-结束符号法"><a href="#4-2-3-结束符号法" class="headerlink" title="4.2.3 结束符号法"></a>4.2.3 结束符号法</h4><p>规定某个不会使用的符号作为结束符号，这样只需要检测到这个符号，就把这个符号之前的信息切出来，保证服务端接收的次数和客户端发送的次数一致。</p>
<p>书上采用的是长度信息法。</p>
<h3 id="4-3-大端小端问题"><a href="#4-3-大端小端问题" class="headerlink" title="4.3 大端小端问题"></a>4.3 大端小端问题</h3><p>使用长度信息法的话我们需要将代表长度的Int16或者Int32转化成byte数组发送出去，到了接收方肯定需要再转化成Int类型这个时候会用到一下两个方法：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//len为Int16，将长度转化成byte数组</span></span><br><span class="line"><span class="built_in">byte</span>[] lenBytes = BitConverter.GetBytes(len);</span><br><span class="line"><span class="comment">//将byte数组中的长度转化为Int</span></span><br><span class="line">Int16 bodyLength = BitConverter.ToInt16(readBuff, <span class="number">0</span>);</span><br></pre></td></tr></table></figure>
<p>而这两句可能会导致大端小端问题</p>
<h4 id="4-3-1-什么是大端小端"><a href="#4-3-1-什么是大端小端" class="headerlink" title="4.3.1 什么是大端小端"></a>4.3.1 什么是大端小端</h4><p>学过计组的可能有了解，不同的CPU使用了不同的存储方式，这是由于一些历史的因素导致的。</p>
<p>大端：高地址存低字节，低地址存高字节</p>
<p>小端：低地址存低字节，高地址存高字节</p>
<p>例如：<img src="https://s2.loli.net/2022/10/18/H4ZrXBbIhmWwexl.png" alt=""></p>
<p>这是一个16位的内存，我们将他转化位一个Int16类型的数</p>
<p>如果是个大端的系统那么高地址存低字节，高地址的00000010会转化为2，低地址的存高字节，低地址的00000001会转化成256，整个数据会转化为256+2=258。</p>
<p>如果是个小端的系统那么高地址存高字节，高地址的00000010会转化为512，低地址存低字节，低地址的00000001会转化成1，整个数据会转化成512+1=513。</p>
<p>所以同样的一串二进制数在不同的系统中会解析成不同的数字，非常的不方便。</p>
<h4 id="4-3-2-如何解决大端小端的问题"><a href="#4-3-2-如何解决大端小端的问题" class="headerlink" title="4.3.2 如何解决大端小端的问题"></a>4.3.2 如何解决大端小端的问题</h4><p>既然计算机本身不能解决问题，那么我们就用程序来解决问题，我们首先规定数据的传输必须使用小端输入，因为我们必须使用小端编码，所以我们需要判断系统是否是小端，如果不是小端，那么我们就使用Reverse函数来转化成小端模式。</p>
<p>一下是发送数据的大致代码</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Send</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">string</span> sendStr = inputFeild.text;</span><br><span class="line">    <span class="built_in">byte</span>[] bodyBytes = System.Text.Encoding.Default.GetBytes(sendStr);</span><br><span class="line">    Int16 len = (Int16)bodyBytes.Length;</span><br><span class="line">    <span class="built_in">byte</span>[] lenBytes = BitConverter.GetBytes(len);</span><br><span class="line">    <span class="comment">//判断是否是小端</span></span><br><span class="line">    <span class="keyword">if</span> (!BitConverter.IsLittleEndian)</span><br><span class="line">    &#123;</span><br><span class="line">        lenBytes.Reverse();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">byte</span>[] sendBytes = lenBytes.Concat(bodyBytes).ToArray();</span><br><span class="line">    socket.Send(sendBytes);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<font color='red'>拓展:</font>

<p>虽然BitConverter.IsLittleEndian可以直接调用并判断计算机的大小端，但是我们也应该学习一下如何去实现判断大小端的方法，我分别用C#，C++，C三种语言实现一下：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//C#</span></span><br><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">class</span> <span class="title">MainClass</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>(<span class="params"><span class="built_in">string</span>[] args</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">int</span> i = <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">byte</span>[] a = BitConverter.GetBytes(i);</span><br><span class="line">        <span class="keyword">if</span> (a[<span class="number">0</span>] == <span class="number">1</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;小端&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;大端&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>C++：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">union</span> <span class="title class_">thenumber</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="type">int</span> a;</span><br><span class="line">		<span class="type">char</span> b;</span><br><span class="line">	&#125;;</span><br><span class="line"></span><br><span class="line">	thenumber c;</span><br><span class="line">	c.a = <span class="number">1</span>;</span><br><span class="line">	<span class="keyword">if</span> (c.b == <span class="number">1</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		cout &lt;&lt; <span class="string">&quot;小端&quot;</span> &lt;&lt; endl;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">else</span></span><br><span class="line">	&#123;</span><br><span class="line">		cout &lt;&lt; <span class="string">&quot;大端&quot;</span> &lt;&lt; endl;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>C++使用了union的特点来测试：不懂union可以去查一下</p>
<p>C：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">&#123;</span><br><span class="line">	<span class="type">int</span> a = <span class="number">1</span>;</span><br><span class="line">	<span class="type">char</span> b = *(<span class="type">char</span> *)&amp;a;</span><br><span class="line">	<span class="keyword">if</span> (b == <span class="number">1</span>)</span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;%s&quot;</span>, <span class="string">&quot;小端&quot;</span>);</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">else</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">printf</span>(<span class="string">&quot;%s&quot;</span>, <span class="string">&quot;大端&quot;</span>);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>C++完全兼容C，所以也可以用这种方法</p>
<font color='red'>拓展结束</font>

<p>发送端的代码搞定了，但是如果接收端是大端的话BitConverter.ToInt16也会出问题，所以我们要自己解析接收到的代码：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span>	<span class="title">OnReceiveData</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span>(buffcount&lt;<span class="number">2</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Int16 bodyLength=(<span class="built_in">short</span>)((readbuff[<span class="number">1</span>]&lt;&lt;<span class="number">8</span>)|readbuff[<span class="number">0</span>]);</span><br><span class="line">    <span class="comment">//其他消息的处理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这段代码理解起来可能稍微有点难理解，首先我们先将第二个字节向左移动八位，也就是乘了一个256然后与第一个字节相与，这在二进制中代表的是相加。最后再强转成Int16类型的数值。</p>
<h3 id="4-4-不完整发送"><a href="#4-4-不完整发送" class="headerlink" title="4.4 不完整发送"></a>4.4 不完整发送</h3><p>之前介绍到TCP在发送时会先将需要发送的数据先放入操作系统的缓冲区，如果操作系统缓冲区设计的比较小，在网络环境较差的环境下，导致操作系统的缓冲区满了，这个时候再发送，溢出的部分会被抛弃掉引发不完整发送，虽然再网络通畅的环境下，Send只发送部分数据的改率不高，很多商业游戏都没有处理这种情况，但是我们要知道这种情况如何去处理。</p>
<h4 id="4-4-1-解决方案"><a href="#4-4-1-解决方案" class="headerlink" title="4.4.1 解决方案"></a>4.4.1 解决方案</h4><p>要让数据能够完整发送，最好的方法就是将这个数据在发送前保存起来，如果发生不完整发送，在回调函数中继续发送数据。</p>
<p>代码实现：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//定义缓冲区</span></span><br><span class="line"><span class="built_in">byte</span>[] sendBytes = <span class="keyword">new</span> <span class="built_in">byte</span>[<span class="number">1024</span>];</span><br><span class="line"><span class="comment">//缓冲区偏移量</span></span><br><span class="line"><span class="built_in">int</span> readIdx = <span class="number">0</span>;</span><br><span class="line"><span class="comment">//缓冲区剩余长度</span></span><br><span class="line"><span class="built_in">int</span> length = <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Send</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    sendBytes = 要发送的数据;</span><br><span class="line">    length = sendBytes.Length;</span><br><span class="line">    readIdx = <span class="number">0</span>;</span><br><span class="line">    socket.BeginSend(sendBytes, <span class="number">0</span>, length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SendCallback</span>(<span class="params">IAsyncResult ar</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Socket socket = (Socket)ar.AsyncState;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">int</span> count = socket.EndSend(ar);</span><br><span class="line">    readIdx += count;</span><br><span class="line">    length -= count;</span><br><span class="line">    <span class="keyword">if</span> (length &gt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        socket.BeginSend(sendBytes, readIdx, length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>解析以上这段代码，首先我们定义了自己的缓冲区，缓冲区偏移量，缓冲区的剩余长度。然后分别赋值，调用BeginSend，发送完成调用回调函数，将偏移量加上已发送数据，定位到发送到哪了，将剩下的长度减去发送的长度，用于判断是否发完，没发完继续调用BeginSend继续发送。</p>
<p>这种方法已经解决了大部分的问题，但是如果我们在还没来的及调用回调函数发出剩下的数据时又再次调用了Send，那么sendBytes，length，readIdx都会被重置，该发不完还是发不完（你这不是什么都没解决嘛），为此我们有必要牺牲一点点内存来构造一个加强版的缓冲区。我们将设计一个缓冲队列，每一次发送新的消息都将新消息写入队列的末尾，等前面的消息读取完，就将前面的消息弹出队列，开始读取后面的消息。</p>
<p><font color='red'>补充：</font>如果你有过开发动作游戏的经验的话，你就会发现很多设计都有一曲同工之妙，比如你处理玩家的一系列指令的话，也可以将这些指令先放入一个队列，程序再一个一个调用。而且如果有什么特殊攻击的话也可以通过此判断。</p>
<p>我们来看看实现的代码：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ByteArray</span></span><br><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">class</span> <span class="title">ByteArray</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//缓冲区</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">byte</span>[] bytes;</span><br><span class="line">    <span class="comment">//读写位置</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> readIdx = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> writeIdx = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//数据长度</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> length &#123; <span class="keyword">get</span> &#123; <span class="keyword">return</span> writeIdx - readIdx; &#125; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ByteArray</span>(<span class="params"><span class="built_in">byte</span>[] defaultBytes</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        bytes = defaultBytes;</span><br><span class="line">        readIdx = <span class="number">0</span>;</span><br><span class="line">        writeIdx = defaultBytes.Length;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>队列我们直接使用Queue</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Queue&lt;ByteArray&gt; writeQueuq=<span class="keyword">new</span> Queue&lt;ByteArray&gt;();</span><br></pre></td></tr></table></figure>
<p>看一下如何使用呢：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">Queue&lt;ByteArray&gt; writeQueue = <span class="keyword">new</span> Queue&lt;ByteArray&gt;();</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Send</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">byte</span>[] sendByte = 要发送的数据;</span><br><span class="line">    ByteArray ba = <span class="keyword">new</span> ByteArray(sendByte);</span><br><span class="line">    writeQueue.Enqueue(ba);</span><br><span class="line">    socket.BeginSend(ba.bytes, ba.readIdx, ba.length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SendCallback</span>(<span class="params">IAsyncResult ar</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Socket socket = (Socket)ar.AsyncState;</span><br><span class="line">    <span class="built_in">int</span> count = socket.EndSend(ar);</span><br><span class="line">    ByteArray ba = writeQueue.First();</span><br><span class="line">    ba.readIdx += count;</span><br><span class="line">    <span class="keyword">if</span> (ba.length == <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        writeQueue.Dequeue();</span><br><span class="line">        ba = writeQueue.First();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (ba != <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        socket.BeginSend(ba.bytes, ba.readIdx, ba.length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>​    </p>
<p>这样的程序看似已经非常完善了，但是我们依旧忽略了一个问题，这是个异步方法，回调函数和BeginSend运行于不同的线程，不同的线程操作同一段内存一定要注意一个问题，那就是线程冲突，设想一种情况：我们先发一条消息，这个消息放进了队列，此时开始运行Send，这个时候Send函数又被调用，将另一条消息放入队列这个时候回调函数刚还执行完并将第一条消息发送出去，再次调用了BeginSend，将第二条消息发送出去了，原线程也在此时调用了BeginSend，于是一条消息发送了两次。</p>
<p>画个图理解以下<img src="https://s2.loli.net/2022/10/19/9Fu3RJ4XCWhrHAU.png" alt=""></p>
<p>学过操作系统的都明白这中情况怎么处理，简单来说就是加锁：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">Queue&lt;ByteArray&gt; writeQueue = <span class="keyword">new</span> Queue&lt;ByteArray&gt;();</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Send</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">byte</span>[] sendByte = 要发送的数据;</span><br><span class="line">    ByteArray ba = <span class="keyword">new</span> ByteArray(sendByte);</span><br><span class="line">    <span class="built_in">int</span> count=<span class="number">0</span>;</span><br><span class="line">    <span class="keyword">lock</span>(writeQueue)</span><br><span class="line">    &#123;</span><br><span class="line">        writeQueue.Enqueue(ba);</span><br><span class="line">        count=writeQueue.Count;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(count==<span class="number">1</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        socket.BeginSend(ba.bytes, ba.readIdx, ba.length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SendCallback</span>(<span class="params">IAsyncResult ar</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Socket socket = (Socket)ar.AsyncState;</span><br><span class="line">    <span class="built_in">int</span> count = socket.EndSend(ar);</span><br><span class="line">    ByteArray ba;</span><br><span class="line">    <span class="keyword">lock</span>(writeQueue)</span><br><span class="line">    &#123;</span><br><span class="line">        ba=writeQueue.First;</span><br><span class="line">    &#125;</span><br><span class="line">    ba.readIdx += count;</span><br><span class="line">    <span class="keyword">if</span> (count == ba.length)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">lock</span>(writeQueue)</span><br><span class="line">        &#123;</span><br><span class="line">            writeQueue.Dequeue();</span><br><span class="line">        	ba = writeQueue.First();</span><br><span class="line">        &#125;     </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (ba != <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        socket.BeginSend(ba.bytes, ba.readIdx, ba.length, <span class="number">0</span>, SendCallback, socket);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="4-5-高效接收数据"><a href="#4-5-高效接收数据" class="headerlink" title="4.5 高效接收数据"></a>4.5 高效接收数据</h3><p>既然要追求极致那就贯彻到底，接收数据我们也可以使用之前定义的ByteArray</p>
<p>首先我们要解决两个问题</p>
<p>1.能读写信息</p>
<p>2.缓冲区不够长时自动扩张缓冲区</p>
<p>那么我们就来完善以下ByteArray代码：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">ByteArray</span>  &#123;</span><br><span class="line">	<span class="comment">//默认大小</span></span><br><span class="line">	<span class="keyword">const</span> <span class="built_in">int</span> DEFAULT_SIZE = <span class="number">1024</span>;</span><br><span class="line">	<span class="comment">//初始大小</span></span><br><span class="line">	<span class="built_in">int</span> initSize = <span class="number">0</span>;</span><br><span class="line">	<span class="comment">//缓冲区</span></span><br><span class="line">	<span class="keyword">public</span> <span class="built_in">byte</span>[] bytes;</span><br><span class="line">	<span class="comment">//读写位置</span></span><br><span class="line">	<span class="keyword">public</span> <span class="built_in">int</span> readIdx = <span class="number">0</span>;</span><br><span class="line">	<span class="keyword">public</span> <span class="built_in">int</span> writeIdx = <span class="number">0</span>;</span><br><span class="line">	<span class="comment">//容量</span></span><br><span class="line">	<span class="keyword">private</span> <span class="built_in">int</span> capacity = <span class="number">0</span>;</span><br><span class="line">	<span class="comment">//剩余空间</span></span><br><span class="line">	<span class="keyword">public</span> <span class="built_in">int</span> remain &#123; <span class="keyword">get</span> &#123; <span class="keyword">return</span> capacity-writeIdx; &#125;&#125;</span><br><span class="line">	<span class="comment">//数据长度</span></span><br><span class="line">	<span class="keyword">public</span> <span class="built_in">int</span> length &#123; <span class="keyword">get</span> &#123; <span class="keyword">return</span> writeIdx-readIdx; &#125;&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//构造函数</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">ByteArray</span>(<span class="params"><span class="built_in">int</span> size = DEFAULT_SIZE</span>)</span>&#123;</span><br><span class="line">		bytes = <span class="keyword">new</span> <span class="built_in">byte</span>[size];</span><br><span class="line">		capacity = size;</span><br><span class="line">		initSize = size;</span><br><span class="line">		readIdx = <span class="number">0</span>;</span><br><span class="line">		writeIdx = <span class="number">0</span>;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//构造函数</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="title">ByteArray</span>(<span class="params"><span class="built_in">byte</span>[] defaultBytes</span>)</span>&#123;</span><br><span class="line">		bytes = defaultBytes;</span><br><span class="line">		capacity = defaultBytes.Length;</span><br><span class="line">		initSize = defaultBytes.Length;</span><br><span class="line">		readIdx = <span class="number">0</span>;</span><br><span class="line">		writeIdx = defaultBytes.Length;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//重设尺寸</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ReSize</span>(<span class="params"><span class="built_in">int</span> size</span>)</span>&#123;</span><br><span class="line">		<span class="keyword">if</span>(size &lt; length) <span class="keyword">return</span>;</span><br><span class="line">		<span class="keyword">if</span>(size &lt; initSize) <span class="keyword">return</span>;</span><br><span class="line">		<span class="built_in">int</span> n = <span class="number">1</span>;</span><br><span class="line">		<span class="keyword">while</span>(n&lt;size) n*=<span class="number">2</span>;</span><br><span class="line">		capacity = n;</span><br><span class="line">		<span class="built_in">byte</span>[] newBytes = <span class="keyword">new</span> <span class="built_in">byte</span>[capacity];</span><br><span class="line">		Array.Copy(bytes, readIdx, newBytes, <span class="number">0</span>, writeIdx-readIdx);</span><br><span class="line">		bytes = newBytes;</span><br><span class="line">		writeIdx = length;</span><br><span class="line">		readIdx = <span class="number">0</span>;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//写入数据</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">Write</span>(<span class="params"><span class="built_in">byte</span>[] bs, <span class="built_in">int</span> offset, <span class="built_in">int</span> count</span>)</span>&#123;</span><br><span class="line">		<span class="keyword">if</span>(remain &lt; count)&#123;</span><br><span class="line">			ReSize(length + count);</span><br><span class="line">		&#125;</span><br><span class="line">		Array.Copy(bs, offset, bytes, writeIdx, count);</span><br><span class="line">		writeIdx+=count;</span><br><span class="line">		<span class="keyword">return</span> count;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//读取数据</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">Read</span>(<span class="params"><span class="built_in">byte</span>[] bs, <span class="built_in">int</span> offset, <span class="built_in">int</span> count</span>)</span>&#123;</span><br><span class="line">		count = Math.Min(count, length);</span><br><span class="line">		Array.Copy(bytes, <span class="number">0</span>, bs, offset, count);</span><br><span class="line">		readIdx+=count;</span><br><span class="line">		CheckAndMoveBytes();</span><br><span class="line">		<span class="keyword">return</span> count;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//检查并移动数据</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">CheckAndMoveBytes</span>()</span>&#123;</span><br><span class="line">		<span class="keyword">if</span>(length &lt; <span class="number">8</span>)&#123;</span><br><span class="line">			MoveBytes();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//移动数据</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">MoveBytes</span>()</span>&#123;</span><br><span class="line">		Array.Copy(bytes, readIdx, bytes, <span class="number">0</span>, length);</span><br><span class="line">		writeIdx = length;</span><br><span class="line">		readIdx = <span class="number">0</span>;</span><br><span class="line">	&#125; </span><br><span class="line"></span><br><span class="line">	<span class="comment">//读取Int16</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> Int16 <span class="title">ReadInt16</span>()</span>&#123;</span><br><span class="line">		<span class="keyword">if</span>(length &lt; <span class="number">2</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">		Int16 ret = BitConverter.ToInt16(bytes, readIdx);</span><br><span class="line">		readIdx += <span class="number">2</span>;</span><br><span class="line">		CheckAndMoveBytes();</span><br><span class="line">		<span class="keyword">return</span> ret;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//读取Int32</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> Int32 <span class="title">ReadInt32</span>()</span>&#123;</span><br><span class="line">		<span class="keyword">if</span>(length &lt; <span class="number">4</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">		Int32 ret = BitConverter.ToInt32(bytes, readIdx);</span><br><span class="line">		readIdx += <span class="number">4</span>;</span><br><span class="line">		CheckAndMoveBytes();</span><br><span class="line">		<span class="keyword">return</span> ret;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">	<span class="comment">//打印缓冲区</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">ToString</span>()</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> BitConverter.ToString(bytes, readIdx, length);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//打印调试信息</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">Debug</span>()</span>&#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="built_in">string</span>.Format(<span class="string">&quot;readIdx(&#123;0&#125;) writeIdx(&#123;1&#125;) bytes(&#123;2&#125;)&quot;</span>,</span><br><span class="line">			readIdx,</span><br><span class="line">			writeIdx,</span><br><span class="line">			BitConverter.ToString(bytes, <span class="number">0</span>, capacity)</span><br><span class="line">		);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>使用方法：</p>
<figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Echo</span> : <span class="title">MonoBehaviour</span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//定义套接字</span></span><br><span class="line">	Socket socket;</span><br><span class="line">	<span class="comment">//UGUI</span></span><br><span class="line">	<span class="keyword">public</span> InputField InputFeld;</span><br><span class="line">	<span class="keyword">public</span> Text text;</span><br><span class="line">	<span class="comment">//接收缓冲区</span></span><br><span class="line">	ByteArray readBuff = <span class="keyword">new</span> ByteArray();</span><br><span class="line">	<span class="comment">//发送缓冲区</span></span><br><span class="line">	Queue&lt;ByteArray&gt; writeQueue = <span class="keyword">new</span> Queue&lt;ByteArray&gt;();</span><br><span class="line">	<span class="built_in">bool</span> isSending = <span class="literal">false</span>;</span><br><span class="line">	<span class="comment">//显示文字</span></span><br><span class="line">	<span class="built_in">string</span> recvStr = <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">	<span class="comment">//点击连接按钮</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Connection</span>()</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="comment">//Socket</span></span><br><span class="line">		socket = <span class="keyword">new</span> Socket(AddressFamily.InterNetwork,</span><br><span class="line">			SocketType.Stream, ProtocolType.Tcp);</span><br><span class="line">		<span class="comment">//为了精简代码：使用同步Connect</span></span><br><span class="line">		<span class="comment">//             不考虑抛出异常</span></span><br><span class="line">		socket.Connect(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8888</span>);</span><br><span class="line"></span><br><span class="line">		socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, </span><br><span class="line">							readBuff.remain, <span class="number">0</span>, ReceiveCallback, socket);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//Receive回调</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ReceiveCallback</span>(<span class="params">IAsyncResult ar</span>)</span>&#123;</span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			Socket socket = (Socket) ar.AsyncState;</span><br><span class="line">			<span class="comment">//获取接收数据长度</span></span><br><span class="line">			<span class="built_in">int</span> count = socket.EndReceive(ar);</span><br><span class="line">			readBuff.writeIdx+=count;</span><br><span class="line">			<span class="comment">//处理二进制消息</span></span><br><span class="line">			OnReceiveData();</span><br><span class="line">			<span class="comment">//继续接收数据</span></span><br><span class="line">			<span class="keyword">if</span>(readBuff.remain &lt; <span class="number">8</span>)&#123;</span><br><span class="line">				readBuff.MoveBytes();</span><br><span class="line">				readBuff.ReSize(readBuff.length*<span class="number">2</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, </span><br><span class="line">				readBuff.remain, <span class="number">0</span>, ReceiveCallback, socket);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">catch</span> (SocketException ex)&#123;</span><br><span class="line">			Debug.Log(<span class="string">&quot;Socket Receive fail&quot;</span> + ex.ToString());</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">//</span></span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">OnReceiveData</span>()</span>&#123;</span><br><span class="line">		Debug.Log(<span class="string">&quot;[Recv 1] length  =&quot;</span> + readBuff.length);</span><br><span class="line">		Debug.Log(<span class="string">&quot;[Recv 2] readbuff=&quot;</span> + readBuff.ToString());</span><br><span class="line">		<span class="keyword">if</span>(readBuff.length &lt;= <span class="number">2</span>) </span><br><span class="line">			<span class="keyword">return</span>;</span><br><span class="line">		<span class="comment">//消息长度</span></span><br><span class="line">		<span class="built_in">int</span> readIdx = readBuff.readIdx;</span><br><span class="line">		<span class="built_in">byte</span>[] bytes =readBuff.bytes; </span><br><span class="line">		Int16 bodyLength = (Int16)((bytes[readIdx+<span class="number">1</span>] &lt;&lt; <span class="number">8</span> )| bytes[readIdx]);</span><br><span class="line">		<span class="keyword">if</span>(readBuff.length &lt; bodyLength)</span><br><span class="line">			<span class="keyword">return</span>;</span><br><span class="line">		readBuff.readIdx+=<span class="number">2</span>; </span><br><span class="line">		Debug.Log(<span class="string">&quot;[Recv 3] bodyLength=&quot;</span> +bodyLength);</span><br><span class="line">		<span class="comment">//消息体</span></span><br><span class="line">		<span class="built_in">byte</span>[] stringByte = <span class="keyword">new</span> <span class="built_in">byte</span>[bodyLength];</span><br><span class="line">		readBuff.Read(stringByte, <span class="number">0</span>, bodyLength);</span><br><span class="line">		<span class="built_in">string</span> s = System.Text.Encoding.UTF8.GetString(stringByte);</span><br><span class="line"></span><br><span class="line">		Debug.Log(<span class="string">&quot;[Recv 4] s=&quot;</span> +s);</span><br><span class="line">;</span><br><span class="line">		Debug.Log(<span class="string">&quot;[Recv 5] readbuff=&quot;</span> + readBuff.ToString());</span><br><span class="line">		<span class="comment">//消息处理</span></span><br><span class="line">		recvStr = s + <span class="string">&quot;\n&quot;</span> + recvStr;</span><br><span class="line">		<span class="comment">//继续读取消息</span></span><br><span class="line">		<span class="keyword">if</span>(readBuff.length &gt; <span class="number">2</span>)&#123;</span><br><span class="line">			OnReceiveData();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里就不多做介绍了，具体的内容和实现可以多看看代码</p>
</div>
        </div>
        
            <div class="kratos-copyright text-center clearfix">
                <h5 itemprop="copyrightNotice">本作品采用 <a rel="license nofollow" target="_blank" href="https://creativecommons.org/licenses/by-sa/4.0/">知识共享署名-相同方式共享 4.0 国际许可协议</a> 进行许可</h5>
            </div>
        
        <footer class="kratos-entry-footer clearfix">
            
                <div class="post-like-donate text-center clearfix" id="post-like-donate">
                
                
                    <a class="share" href="javascript:;"><i class="fa fa-share-alt"></i> 分享</a>
                    <div class="share-wrap" style="display: none;">
    <div class="share-group">
        <a href="javascript:;" class="share-plain qq" onclick="share('qq');" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-qq"></i>
            </div>
        </a>
        <a href="javascript:;" class="share-plain qzone" onclick="share('qzone');" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-star"></i>
            </div>
        </a>
        <a href="javascript:;" class="share-plain weixin pop style-plain" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-weixin"></i>
            </div>
            <div class="share-int">
                <div class="qrcode" id="wechat-qr"></div>
                <p>打开微信“扫一扫”，打开网页后点击屏幕右上角分享按钮</p>
            </div>
        </a>
        <a href="javascript:;" class="share-plain weibo" onclick="share('weibo');" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-weibo"></i>
            </div>
        </a>
        <a href="javascript:;" class="share-plain facebook style-plain" onclick="share('facebook');" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-facebook"></i>
            </div>
        </a>
        <a href="javascript:;" class="share-plain twitter style-plain" onclick="share('twitter');" rel="nofollow">
            <div class="icon-wrap">
                <i class="fa fa-twitter"></i>
            </div>
        </a>
    </div>
    <script type="text/javascript">
        $(()=>{
            new QRCode("wechat-qr", {
                text: "http://example.com/2022/11/16/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91-2/",
                width: 150,
                height: 150,
                correctLevel : QRCode.CorrectLevel.H
            });
        });
        function share(dest) {
            const qqBase        = "https://connect.qq.com/widget/shareqq/index.html?";
            const weiboBase     = "https://service.weibo.com/share/share.php?";
            const qzoneBase     = "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?";
            const facebookBase  = "https://www.facebook.com/sharer/sharer.php?";
            const twitterBase   = "https://twitter.com/intent/tweet?";
            const hostUrl       = "http://example.com/2022/11/16/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91-2/";
            const title         = "「Unity网络游戏开发(2)」";
            const excerpt       = `Unity网络游戏开发第二部分`;
            let _URL;
            switch (dest) {
                case "qq"       : _URL = qqBase+"url="+hostUrl+"&title="+title+"&desc=&summary="+excerpt+"&site=cxpy";     break;
                case "weibo"    : _URL = weiboBase+"url="+hostUrl+"&title="+title+excerpt;                                 break;
                case "qzone"    : _URL = qzoneBase+"url="+hostUrl+"&title="+title+"&desc=&summary="+excerpt+"&site=cxpy";  break;
                case "facebook" : _URL = facebookBase+"u="+hostUrl;                                                        break;
                case "twitter"  : _URL = twitterBase+"text="+title+excerpt+"&url="+hostUrl;                                break;
            }
            window.open(_URL);
        };
    </script>
</div>
                
                </div>
            
            <div class="footer-tag clearfix">
                <div class="pull-left">
                <i class="fa fa-tags"></i>
                    <a class="tag-none-link" href="/tags/unity/" rel="tag">unity</a>, <a class="tag-none-link" href="/tags/%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F/" rel="tag">网络游戏</a>
                </div>
                <div class="pull-date">
                    <time datetime="2022-11-16T07:48:51.343Z" itemprop="dateModified">最后编辑：2022-11-16</time>
                </div>
            </div>
        </footer>
    </div>
    
        <nav class="navigation post-navigation clearfix" role="navigation">
            
            <div class="nav-previous clearfix">
                <a title=" Unity网络游戏开发(1)" href="/2022/11/16/Unity网络游戏开发/Unity网络游戏开发-1/">&lt; 上一篇</a>
            </div>
            
            
            <div class="nav-next clearfix">
                <a title=" Unity网络游戏开发(3)" href="/2022/11/16/Unity网络游戏开发/Unity网络游戏开发-3/">下一篇 &gt;</a>
            </div>
            
        </nav>
    
    
</article>

        

            </section>

        

                
            

<section id="kratos-widget-area" class="col-md-4 hidden-xs hidden-sm">
    <!-- 文章和页面根据splitter来分割，没有的话就从头开始设置为sticky -->
    
    
                <aside id="krw-about" class="widget widget-kratos-about clearfix">
    <div class="photo-background"></div>
    <div class="photo-wrapper clearfix">
        <div class="photo-wrapper-tip text-center">
            <img class="about-photo" src="/images/avatar.webp" loading="lazy" decoding="auto" />
        </div>
    </div>
    <div class="textwidget">
        <p class="text-center"></p>
    </div>
    <div class="site-meta">
        <a class="meta-item" href="/archives/">
            <span class="title">
                文章
            </span>
            <span class="count">
                39
            </span>
        </a>
        <a class="meta-item" href="/categories/">
            <span class="title">
                分类
            </span>
            <span class="count">
                7
            </span>
        </a>
        <a class="meta-item" href="/tags/">
            <span class="title">
                标签
            </span>
            <span class="count">
                10
            </span>
        </a>
    </div>
</aside>
            
                    <div class="sticky-area">
                
                
  <aside id="krw-categories" class="widget widget-kratos-categories clearfix">
    <h4 class="widget-title"><i class="fa fa-folder"></i>分类目录</h4>
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/C-%E7%9F%A5%E8%AF%86/">C#知识</a><span class="category-list-count">9</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Lua/">Lua</a><span class="category-list-count">10</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Unity%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/">Unity网络游戏开发</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B4/">宝可梦小说</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AC%94%E8%AE%B0/">操作系统笔记</a><span class="category-list-count">10</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AC%94%E8%AE%B0/">数据结构笔记</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E9%9A%8F%E6%83%B3/">随想</a><span class="category-list-count">1</span></li></ul>
  </aside>


            
                
  <aside id="krw-tags" class="widget widget-kratos-tags clearfix">
    <h4 class="widget-title"><i class="fa fa-tags"></i>标签聚合</h4>
      <div class="tag-clouds">
        <a href="/tags/C/" style="font-size: 0.75em;">C#</a> <a href="/tags/Lua/" style="font-size: 0.8em;">Lua</a> <a href="/tags/unity/" style="font-size: 0.65em;">unity</a> <a href="/tags/%E5%AE%9D%E5%8F%AF%E6%A2%A6/" style="font-size: 0.7em;">宝可梦</a> <a href="/tags/%E5%BD%B1%E8%AF%84/" style="font-size: 0.6em;">影评</a> <a href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" style="font-size: 0.8em;">操作系统</a> <a href="/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/" style="font-size: 0.6em;">数据结构</a> <a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size: 0.6em;">算法</a> <a href="/tags/%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F/" style="font-size: 0.65em;">网络游戏</a> <a href="/tags/%E9%9A%8F%E6%83%B3/" style="font-size: 0.6em;">随想</a>
      </div>
  </aside>

            
                
  <aside id="krw-posts" class="widget widget-kratos-posts">
  <h4 class="widget-title"><i class="fa fa-file"></i>最新文章</h4>
  <div class="tab-content">
      <ul class="list-group">
        
        
          
          
            <a class="list-group-item" href="/2025/10/08/%E9%9A%8F%E6%83%B3/%E3%80%8A%E6%98%9F%E4%B8%89%E5%9B%BD%E3%80%8B%E5%90%90%E6%A7%BD/"><i class="fa  fa-book"></i> 《星三国》吐槽</a>
            
          
        
          
          
            <a class="list-group-item" href="/2023/08/14/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B4/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B45/"><i class="fa  fa-book"></i> 宝可梦小说第五章</a>
            
          
        
          
          
            <a class="list-group-item" href="/2023/08/13/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B4/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B44/"><i class="fa  fa-book"></i> 宝可梦小说第四章</a>
            
          
        
          
          
            <a class="list-group-item" href="/2023/08/10/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B4/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B43/"><i class="fa  fa-book"></i> 宝可梦小说第三章</a>
            
          
        
          
          
            <a class="list-group-item" href="/2023/08/09/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B4/%E5%AE%9D%E5%8F%AF%E6%A2%A6%E5%B0%8F%E8%AF%B42/"><i class="fa  fa-book"></i> 宝可梦小说第二章</a>
            
          
        
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
          
            
      </ul>
  </div>
  </aside>

            
    </div>
</section>
        
        </div>
    </div>
</div>
<footer>
    <div id="footer"  >
        <div class="container">
            <div class="row">
                <div class="col-md-6 col-md-offset-3 footer-list text-center">
                    <ul class="kratos-social-icons">
                        
                        
                        
                        
                        
                        
                        
                        
                        
                    </ul>
                    <ul class="kratos-copyright">
                        <div>
                            <li>&copy; 2025 舛羽的笔记小站 版权所有.</li>
                            <li>本站已运行<span id="span_dt">Loading...</span></li>
                        </div>
                        <div>
                            <li>Theme <a href="https://github.com/Candinya/Kratos-Rebirth" target="_blank">Kratos:Rebirth</a></li>
                            <li>Site built with&nbsp;<i class="fa fa-heart throb" style="color:#d43f57"></i>&nbsp;by cy.</li>
                        </div>
                        <div>
                            <li>Powered by <a href="https://hexo.io" target="_blank" rel="nofollow">Hexo</a></li>
                            <li>Hosted on <a href="https://github.io" target="_blank">Github Pages</a></li>
                        </div>
                        <div>
                            
                            
                        </div>
                    </ul>
                </div>
            </div>
        </div>
        <div class="kr-tool text-center">
            <div class="tool">
                
                    <div class="box search-box">
                        <a href="/search/">
                            <span class="fa fa-search"></span>
                        </a>
                    </div>
                
                
                    <div class="box theme-box" id="darkmode-switch">
                        <span class="fa fa-adjust"></span>
                    </div>
                
                
            </div>
            <div class="box gotop-box">
                <span class="fa fa-chevron-up"></span>
            </div>
        </div>
    </div>
</footer>
</div>
</div>

        <script defer src="/vendors/bootstrap@3.3.4/dist/js/bootstrap.min.js"></script>
<script defer src="/vendors/nprogress@0.2.0/nprogress.js"></script>
<script>
    if (!window.kr) {
        window.kr = {};
    }
    window.kr.notMobile = (!(navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)));
    window.kr.siteRoot = "/";
</script>


    <script async src="/js/candy.min.js"></script>




    <script defer src="/vendors/@fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<script defer src="/vendors/clipboard@2.0.6/dist/clipboard.min.js"></script>
<script defer src="/js/kratosr.min.js"></script>
<script defer src="/js/pjax.min.js"></script>



<!-- Extra support for third-party plguins  -->


    <script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        tex2jax: {
            inlineMath: [ ["$","$"], ["\\(","\\)"] ],
            skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
            processEscapes: true
        }
    });
    MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax();
        for (var i = 0; i < all.length; ++i)
            all[i].SourceElement().parentNode.className += ' has-jax';
    });
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML
"></script>
</body>
</html>